查看原文
其他

刨根问底,Kafka消息中间件到底会不会丢消息

ImportNew 2022-09-23

The following article is from 爱笑的架构师 Author 雷架

大型互联网公司一般都会要求消息传递最大限度的不丢失,比如用户服务给代金券服务发送个消息,如果消息丢失会造成用户未收到应得的代金券,最终用户会投诉。


为避免上面类似情况的发生,除了做好补偿措施,更应该在系设计的时候充分考虑各种异常,设计一个稳定、高可用的消息系统。


认识 Kafka


看一下维基百科的定义


Kafka 是分布式发布-订阅消息系统。它最初由 LinkedIn 公司开发,之后成为 Apache 项目的一部分。Kafka是一个分布式的、可划分的、冗余备份的持久性的日志服务。它主要用于处理活跃的流式数据。


Kafka 架构


Kafka 的整体架构非常简单,是显式分布式架构,主要由 Producer、Broker(Kafka) 和 Consumer 组成。


Kafka架构(精简版)

Producer(生产者)可以将数据发布到所选择的 Topic(主题)中。生产者负责将记录分配到 Topic 的哪一个 Partition(分区)中。可以使用循环的方式来简单地实现负载均衡,也可以根据某些语义分区函数(如记录中的 key)来完成。


Consumer(消费者)使用一个 Consumer Group(消费组)名称来进行标识。发布到 Topic 中的每条记录被分配给订阅消费组中的一个消费者实例。消费者实例可以分布在多个进程中或者多个机器上。


Kafka 到底会不会丢失消息?


在讨论 Kafka 是否丢消息前先来了解一下什么是消息传递语义


消息传递语义

Message Delivery Semantic 也就是消息传递语义。简单说就是消息传递过程中消息传递的保证性。


主要分为三种:


  • at most once 最多一次:消息可能丢失也可能被处理,但最多只会被处理一次。
  • at least once 至少一次:消息不会丢失,但可能被处理多次。可能重复,不会丢失。
  • exactly once 精确传递一次:消息被处理且只会被处理一次。不丢失不重复就一次。


理想情况下,肯定是希望系统的消息传递是严格 exactly once。也就是保证不丢失、只会被处理一次,但是很难做到。


回到主角 Kafka。Kafka 有三次消息传递的过程:


  1. 生产者发消息给 Kafka Broker;
  2. Kafka Broker 消息同步和持久化;
  3. Kafka Broker 将消息传递给消费者。


在这三步中每一步都有可能会丢失消息,下面详细分析为什么会丢消息,如何最大限度避免丢失消息。


生产者丢失消息


先介绍一下生产者发送消息的一般流程(部分流程与具体配置项紧密相关,这里先忽略):


  1. 生产者是与 Leader 直接交互,所以先从集群获取 Topic 对应分区的 Leader 元数据;
  2. 获取到 Leader 分区元数据后直接将消息发给过去;
  3. Kafka Broker对应的 Leader 分区收到消息后写入文件持久化;
  4. Follower 拉取 Leader 消息,与 Leader 的数据保持一致;
  5. Follower 消息拉取完毕,需要给 Leader 回复 ACK 确认消息;
  6. Kafka Leader 和 Follower 分区同步完,Leader 分区会给生产者回复 ACK 确认消息。
生产者发送数据流程

生产者采用 Push 模式将数据发布到 Broker。每条消息追加到分区中,顺序写入磁盘。消息写入 Leader 后,Follower 主动与 Leader 进行同步。


Kafka 消息发送有两种方式:同步(Sync)和异步(Async),默认是同步方式。可通过 producer.type 属性进行配置。


Kafka 通过配置 request.required.acks 属性来确认消息的生产:


  • 0 :表示不进行消息接收是否成功的确认,不能保证消息是否发送成功。生成环境基本不会用。
  • 1 :表示当 Leader 接收成功时确认。只要Leader存活就可以保证不丢失,保证了吞吐量。
  • -1 或者 all :表示 Leader 和 Follower 都接收成功时确认。可以最大限度保证消息不丢失,但是吞吐量低。


Kafka Producer 的参数 acks 默认值为1。所以,默认的 Producer 级别是 at least once,并不能 exactly once。


敲黑板了,这里可能会丢消息的!


  • 如果 acks 配置为 0,发生网络抖动消息丢了,生产者不校验 ACK 自然就不知道丢了。
  • 如果 acks 配置为 1 保证 Leader 不丢,但是如果 Leader 挂了,恰好选了一个没有 ACK 的 Follower,那也丢了。
  • all:保证 Leader 和 Follower 不丢。但是,如果网络拥塞没有收到 ACK,会有重重复发送的问题。


Kafka Broker 丢失消息


Kafka Broker 接收到数据后会将数据进行持久化存储,你以为是下面这样的:


消息持久化,无 cache

没想到是这样的:

消息持久化,有cache

操作系统本身有一层缓存,叫做 Page Cache。当往磁盘文件写入的时候,系统会先将数据流写入缓存中,至于什么时候将缓存的数据写入文件中是由操作系统自行决定。


Kafka 提供了一个参数 producer.type,用来控制是不是主动 flush。


  • 如果 Kafka 写入到 mmap 之后就立即 flush,然后再返回 Producer,称为叫同步 (Sync)。

  • 写入 mmap 之后立即返回 Producer 不调用 flush,称为异步 (Async)。


敲黑板了,这里可能会丢消息的!


Kafka 通过多分区多副本机制,已经能最大限度保证数据不会丢失。


如果数据已经写入系统 cache 中,但是还没来得及刷入磁盘。此时,突然机器宕机或者掉电,那消息就会丢失。当然这种情况很极端。


消费者丢失消息


消费者通过 Pull 模式主动的去 Kafka 集群拉取消息。与 Producer 相同的是,消费者在拉取消息的时候也是找 Leader 分区去拉取。


多个消费者可以组成一个消费者组(Consumer Group),每个消费者组都有一个组 id。同一个消费组中的消费者可以消费同一 Topic 下不同分区的数据,但是不会出现多个消费者消费同一分区的数据。

消费者群组消费消息

消费者消费的进度通过 offset 保存在 Kafka集群的 __consumer_offsets 这个 Topic中。


消费消息的时候主要分为两个阶段:


  • 标识消息已消费,commit offset 坐标;

  • 处理消息。


敲黑板了,这里可能会丢消息的!


场景一:先 commit 再处理消息。如果在处理消息的时候异常了,但是 offset 已经提交了,这条消息对于该消费者来说就是丢失了,再也不会消费到了。


场景二:先处理消息再 commit。如果在 commit 之前发生异常,下次还会消费该消息,重复消费的问题可以通过业务保证消息幂等性来解决。


总结


那么问题来了,Kafka 到底会不会丢消息?答案是:会!


Kafka可能会在三个阶段丢失消息:


  • 生产者发送数据

  • Kafka Broker 存储数据

  • 消费者消费数据


在生产环境中严格做到 exactly once  其实是难的,同时也会牺牲效率和吞吐量。最佳实践是在业务侧做好补偿机制,万一出现消息丢失可以兜底。


- EOF -

推荐阅读  点击标题可跳转

1、Kafka 生产环境磁盘坏掉了之后的正确处理姿势

2、彻底搞懂 Kafka 消息大小相关参数设置的规则

3、13 张图让你学会 Kafka 分区副本同步限流机制


看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能

点赞和在看就是最大的支持❤️


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存